package com.gl.common.util.oss;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.DeleteObjectsRequest;
import com.aliyun.oss.model.DeleteObjectsResult;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.gl.common.exception.CustomException;
import com.gl.common.util.DateUtils;
import com.gl.common.util.StringUtils;
import com.gl.framework.properties.AliyunOssProperties;

/**
 * Aliyun OSS工具类
 */
@Component
public class AliyunOSSUtils {
	private static final Logger log = LoggerFactory.getLogger(AliyunOSSUtils.class);

	/**
	 * 项目环境
	 */
	@Value("${spring.profiles.active}")
	private String profiles;

	@Autowired
	private AliyunOssProperties aliyunOssProperties;

	/**
	 * 上传MultipartFile文件至私有Bucket
	 *
	 * @param type   文件类型
	 * @param userId 用户ID
	 * @param mfile  文件
	 * @return
	 */
	public String uploadMfile(String type, Long userId, MultipartFile mfile) {
		return this.uploadMfile(type, userId, mfile, false);
	}

	/**
	 * 上传MultipartFile文件
	 *
	 * @param type     文件类型
	 * @param userId   用户ID
	 * @param mfile    文件
	 * @param isPublic 是否公有：true表示文件将上传至公有Bucket，false表示文件将上传至私有Bucket
	 * @return isPublic为true时返回带域名全URL（如：http://xxxx.yyyy.com/dev/id_card/1/934879245027340.jpg）；<br>
	 *         isPublic为false时返回OSS存储路径（不带域名，如：dev/id_card/1/934879245027340.jpg）
	 * @throws CustomException
	 */
	public String uploadMfile(String type, Long userId, MultipartFile mfile, boolean isPublic) throws CustomException {
		String bucketName = isPublic ? aliyunOssProperties.getPublicBucketName() : aliyunOssProperties.getPrivateBucketName();

		// 文件重命名（保证唯一）
		String newFilename = this.getFilename(mfile.getOriginalFilename());

		String fileKey = this.fileObjectName(type, userId, newFilename);

		// 创建OSSClient实例
		OSS ossClient = this.getOSSClient(isPublic);

		try (InputStream inputStream = mfile.getInputStream();) {
			ObjectMetadata objectMetadata = new ObjectMetadata();
			// 设置数据流里有多少个字节可以读取
	        objectMetadata.setContentLength(inputStream.available());
	        objectMetadata.setCacheControl("no-cache");
            objectMetadata.setHeader("Pragma", "no-cache");
	        objectMetadata.setContentType(mfile.getContentType());
	        objectMetadata.setContentDisposition("inline;filename=" + newFilename);

			// 上传文件
			ossClient.putObject(bucketName, fileKey, inputStream, objectMetadata);

			return isPublic ? (aliyunOssProperties.getPublicBucketHost() + "/" + fileKey) : fileKey;
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new CustomException("上传失败，请重试");
		} finally {
			// 关闭OSSClient
			ossClient.shutdown();
		}
	}

	/**
	 * 上传File文件
	 *
	 * @param type     文件类型
	 * @param userId   用户ID
	 * @param file     文件
	 * @param isPublic 是否公有：true表示文件将上传至公有Bucket，false表示文件将上传至私有Bucket
	 * @return isPublic为true时返回带域名全URL（如：http://xxxx.yyyy.com/dev/id_card/1/934879245027340.jpg）；<br>
	 *         isPublic为false时返回OSS存储路径（不带域名，如：dev/id_card/1/934879245027340.jpg）
	 * @throws CustomException
	 */
	public String uploadFile(String type, Long userId, File file, boolean isPublic) throws CustomException {
		String bucketName = isPublic ? aliyunOssProperties.getPublicBucketName() : aliyunOssProperties.getPrivateBucketName();

		// 文件重命名（保证唯一）
		String newFilename = this.getFilename(file.getName());

		// 指定上传文件到OSS时需要指定包含文件后缀在内的完整路径，例如：dev/id_card/1/934879245027340.jpg
		String fileKey = this.fileObjectName(type, userId, newFilename);

		// 创建OSSClient实例
		OSS ossClient = this.getOSSClient(isPublic);
		try {
			// 创建PutObjectRequest对象。
			PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileKey, file);

			// 上传文件
			ossClient.putObject(putObjectRequest);

			return isPublic ? (aliyunOssProperties.getPublicBucketHost() + "/" + fileKey) : fileKey;
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new CustomException("上传失败，请重试");
		} finally {
			ossClient.shutdown();
		}
	}

	/**
	 * 上传字符串
	 *
	 * @param type     文件类型
	 * @param userId   用户ID
	 * @param content  待上传的字符串
	 * @param isPublic 是否公有：true表示文件将上传至公有Bucket，false表示文件将上传至私有Bucket
	 * @return isPublic为true时返回带域名全URL（如：http://xxxx.yyyy.com/dev/id_card/1/934879245027340.jpg）；<br>
	 *         isPublic为false时返回OSS存储路径（不带域名，如：dev/id_card/1/934879245027340.jpg）
	 */
	public String uploadStr(String type, Long userId, String content, boolean isPublic) {
		String bucketName = isPublic ? aliyunOssProperties.getPublicBucketName() : aliyunOssProperties.getPrivateBucketName();

		// 文件重命名（保证唯一）
		String newFilename = this.getFilename("str.jpg");

		String fileKey = this.fileObjectName(type, userId, newFilename);

		// 创建OSSClient实例
		OSS ossClient = this.getOSSClient(isPublic);

		PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileKey, new ByteArrayInputStream(content.getBytes()));

		// 上传字符串
		ossClient.putObject(putObjectRequest);

		// 关闭OSSClient
		ossClient.shutdown();

		return isPublic ? (aliyunOssProperties.getPublicBucketHost() + "/" + fileKey) : fileKey;
	}

	/**
	 * 上传Byte数组
	 *
	 * @param type     文件类型
	 * @param userId   用户ID
	 * @param bytes    待上传的Byte数组
	 * @param isPublic 是否公有：true表示文件将上传至公有Bucket，false表示文件将上传至私有Bucket
	 * @return isPublic为true时返回带域名全URL（如：http://xxxx.yyyy.com/dev/id_card/1/934879245027340.jpg）；<br>
	 *         isPublic为false时返回OSS存储路径（不带域名，如：dev/id_card/1/934879245027340.jpg）
	 */
	public String uploadBytes(String type, Long userId, byte[] bytes, boolean isPublic) {
		String bucketName = isPublic ? aliyunOssProperties.getPublicBucketName() : aliyunOssProperties.getPrivateBucketName();

		// 文件重命名（保证唯一）
		String newFilename = this.getFilename("byte.jpg");

		String fileKey = this.fileObjectName(type, userId, newFilename);

		// 创建OSSClient实例
		OSS ossClient = this.getOSSClient(isPublic);

		ossClient.putObject(bucketName, fileKey, new ByteArrayInputStream(bytes));

		// 关闭OSSClient
		ossClient.shutdown();

		return isPublic ? (aliyunOssProperties.getPublicBucketHost() + "/" + fileKey) : fileKey;
	}

	/**
	 * 获取单个文件的签名URL（URL有过期时间）
	 * <p>说明：只有私有Bucket中的文件需要获取其签名URL进行访问</p>
	 *
	 * @param fileKey 文件在OSS存储的路径，如：dev/id_card/1/934879245027340.jpg
	 * @return
	 */
	public String getFileUrl(String fileKey) {
		if (StringUtils.isBlank(fileKey)) {
			return null;
		}
		// 带域名的链接无效查询
		if (StringUtils.startsWith(fileKey, "http")) {
			return fileKey;
		}
		return getFileUrls(Collections.singletonList(fileKey)).get(fileKey);
	}

	/**
	 * 获取多个文件的签名URL（URL有过期时间）
	 * <p>说明：只有私有Bucket中的文件需要获取其签名URL进行访问</p>
	 *
	 * @param fileKeys 文件在OSS存储的路径组，如：['dev/id_card/1/934879245027340.jpg']
	 * @return
	 */
	public Map<String, String> getFileUrls(List<String> fileKeys) {
		if (StringUtils.isEmpty(fileKeys)) {
			return Collections.emptyMap();
		}

		String bucketName = aliyunOssProperties.getPrivateBucketName();
		// 签名有效期（单位：天）
		Integer expireTime = aliyunOssProperties.getExpireTime();

		Map<String, String> map = new HashMap<>(fileKeys.size());

		// 计算URL过期日期
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DAY_OF_MONTH, expireTime);
		Date expiration = cal.getTime();

		// 创建OSSClient实例
		OSS ossClient = this.getOSSClient(false);
		try {
			for (String fileKey : fileKeys) {
				if (StringUtils.isBlank(fileKey)) {
					continue;
				}
				if (StringUtils.startsWith(fileKey, "http")) {
					// 带域名的链接不用查询
					map.put(fileKey, fileKey);
				} else {
					URL url = ossClient.generatePresignedUrl(bucketName, fileKey, expiration);
					map.put(fileKey, url.toString());
				}
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		} finally {
			// 关闭OSSClient
			ossClient.shutdown();
		}

		return map;
	}

	/**
	 * 删除单个文件（私有Bucket）
	 *
	 * @param fileKey 文件在OSS存储的路径（不带域名，如：dev/id_card/1/934879245027340.jpg）
	 */
	public void deletePrivateOneFile(String fileKey) {
		this.deleteOneFile(fileKey, false);
	}

	/**
	 * 删除单个文件（公有Bucket）
	 *
	 * @param fileKey 文件在OSS存储的路径（不带域名，如：dev/id_card/1/934879245027340.jpg）
	 */
	public void deletePublicOneFile(String fileKey) {
		this.deleteOneFile(fileKey, true);
	}

	/**
	 * 删除单个文件
	 *
	 * @param fileKey 文件在OSS存储的路径（不带域名，如：dev/id_card/1/934879245027340.jpg）
	 * @param isPublic 是否公有Bucket：true代表文件在公有Bucket，false代表文件在私有Bucket
	 */
	private void deleteOneFile(String fileKey, boolean isPublic) {
		if (StringUtils.isBlank(fileKey)) {
			return;
		}

		String bucketName = isPublic ? aliyunOssProperties.getPublicBucketName() : aliyunOssProperties.getPrivateBucketName();

		// 创建OSSClient实例
		OSS ossClient = this.getOSSClient(isPublic);

		// 删除文件。如需删除文件夹，请将ObjectName设置为对应的文件夹名称。如果文件夹非空，则需要将文件夹下的所有object删除后才能删除该文件夹。
		ossClient.deleteObject(bucketName, fileKey);

		// 关闭OSSClient
		ossClient.shutdown();
	}

	/**
	 * 删除多个文件
	 *
	 * @param fileKeys 文件在OSS存储的路径组，（不带域名，如：['dev/id_card/1/934879245027340.jpg']）
	 * @param isPublic 是否公有Bucket：true代表文件在公有Bucket，false代表文件在私有Bucket
	 * @return 返回删除失败的文件列表
	 */
	public List<String> deleteFiles(List<String> fileKeys, boolean isPublic) {
		String bucketName = isPublic ? aliyunOssProperties.getPublicBucketName() : aliyunOssProperties.getPrivateBucketName();

		// 创建OSSClient实例
		OSS ossClient = this.getOSSClient(isPublic);

		DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(new DeleteObjectsRequest(bucketName).withKeys(fileKeys));
		List<String> deletedFailObjects = deleteObjectsResult.getDeletedObjects();

		// 关闭OSSClient
		ossClient.shutdown();

		return deletedFailObjects;
	}

	/**
	 * 判断文件是否存在
	 *
	 * @param fileKey 文件在OSS存储的路径，如：dev/id_card/1/934879245027340.jpg
	 * @param isPublic 是否公有Bucket：true代表文件在公有Bucket，false代表文件在私有Bucket
	 * @return
	 */
	public boolean doesFileExist(String fileKey, boolean isPublic) {
		String bucketName = isPublic ? aliyunOssProperties.getPublicBucketName() : aliyunOssProperties.getPrivateBucketName();

		// 创建OSSClient实例
		OSS ossClient = this.getOSSClient(isPublic);

		// 判断文件是否存在。doesObjectExist还有一个参数isOnlyInOSS，如果为true则忽略302重定向或镜像；如果为false，则考虑302重定向或镜像。
		boolean found = ossClient.doesObjectExist(bucketName, fileKey);

		// 关闭OSSClient
		ossClient.shutdown();

		return found;
	}

	/**
	 * 创建OSSClient实例
	 *
	 * @param isPublic
	 * @return
	 */
	private OSS getOSSClient(boolean isPublic) {
//		String endpoint = aliyunOssProperties.getEndpoint();
		String endpoint = isPublic ? aliyunOssProperties.getPublicBucketHost() : aliyunOssProperties.getPrivateBucketHost();
		String accessKeyId = aliyunOssProperties.getAccessKeyId();
		String accessKeySecret = aliyunOssProperties.getAccessKeySecret();

		// 创建ClientConfiguration实例，您可以按照实际情况修改默认参数。
		ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
		// 设置是否支持CNAME。CNAME是指将自定义域名绑定到存储空间上。
		conf.setSupportCname(true);

		return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret, conf);
	}

	/**
	 * 生成文件在OSS存储的路径（如：dev/id_card/1/934879245027340.jpg）
	 *
	 * @param type     文件类型
	 * @param userId   用户ID
	 * @param filename 文件名
	 * @return
	 */
	private String fileObjectName(String type, Long userId, String filename) {
		return new StringBuffer().append(profiles).append("/").append(type).append("/").append(userId).append("/").append(filename).toString();
	}

	/**
	 * 以当前日期+毫秒值重命名
	 *
	 * @param filename
	 * @return
	 */
	private String getFilename(String filename) {
		String suffix = filename.substring(filename.lastIndexOf(".")).toLowerCase();
		String now = DateUtils.format(new Date(), "yyyyMMdd");
		return (now + System.currentTimeMillis() + suffix);
	}

	/**
	 * 根据文件拓展名获取其contentType
	 *
	 * @param filenameExtension 文件拓展名
	 * @return
	 */
	public String getContentType(String filenameExtension) {
		if (filenameExtension.equalsIgnoreCase(".bmp")) {
			return "image/bmp";
		}
		if (filenameExtension.equalsIgnoreCase(".gif")) {
			return "image/gif";
		}
		if (filenameExtension.equalsIgnoreCase(".jpeg") || filenameExtension.equalsIgnoreCase(".jpg") || filenameExtension.equalsIgnoreCase(".png")) {
			return "image/jpeg";
		}
		if (filenameExtension.equalsIgnoreCase(".html")) {
			return "text/html";
		}
		if (filenameExtension.equalsIgnoreCase(".txt")) {
			return "text/plain";
		}
		if (filenameExtension.equalsIgnoreCase(".vsd")) {
			return "application/vnd.visio";
		}
		if (filenameExtension.equalsIgnoreCase(".pptx") || filenameExtension.equalsIgnoreCase(".ppt")) {
			return "application/vnd.ms-powerpoint";
		}
		if (filenameExtension.equalsIgnoreCase(".docx") || filenameExtension.equalsIgnoreCase(".doc")) {
			return "application/msword";
		}
		if (filenameExtension.equalsIgnoreCase(".xml")) {
			return "text/xml";
		}
		return "image/jpeg";
	}

}
